﻿using System;
using System.Globalization;

namespace Microsoft.Xna.Framework.Graphics
{
	/// <summary>
	/// A vertex declaration, which defines per-vertex data.
	/// </summary>
	public class VertexDeclaration : GraphicsResource
	{
		internal VertexElement[] _elements;
		internal int _vertexStride;

		/// <summary>
		/// The number of bytes from one vertex to the next.
		/// </summary>
		public int VertexStride
		{
			get { return _vertexStride; }
		}

		#region Constructor
		/// <summary>
		/// Creates an instance of this object.
		/// </summary>
		/// <param name="elements">An array of per-vertex elements.</param>
		public VertexDeclaration(params VertexElement[] elements)
		{
			if ((elements == null) || (elements.Length == 0))
			{
				throw new ArgumentNullException("elements", FrameworkResources.NullNotAllowed);
			}
			else
			{
				VertexElement[] elementArray = (VertexElement[])elements.Clone();
				this._elements = elementArray;
				this._vertexStride = GetVertexStride(elements);
				Validate(_vertexStride, elements);
			}
		}

		/// <summary>
		/// Creates an instance of this object.
		/// </summary>
		/// <param name="vertexStride">The number of bytes per element.</param>
		/// <param name="elements">An array of per-vertex elements.</param>
		public VertexDeclaration(int vertexStride, params VertexElement[] elements)
		{
			if ((elements == null) || (elements.Length == 0))
			{
				throw new ArgumentNullException("elements", FrameworkResources.NullNotAllowed);
			}
			else
			{
				VertexElement[] elementArray = (VertexElement[])elements.Clone();
				this._elements = elementArray;
				this._vertexStride = vertexStride;

				Validate(vertexStride, elements);
			}
		}
		#endregion

		#region Private Methods
		private int GetTypeSize(VertexElementFormat vertexElementFormat)
		{
			switch (vertexElementFormat)
			{
				case VertexElementFormat.Single:
					return 4;

				case VertexElementFormat.Vector2:
					return 8;

				case VertexElementFormat.Vector3:
					return 12;

				case VertexElementFormat.Vector4:
					return 0x10;

				case VertexElementFormat.Color:
					return 4;

				case VertexElementFormat.Byte4:
					return 4;

				case VertexElementFormat.Short2:
					return 4;

				case VertexElementFormat.Short4:
					return 8;

				case VertexElementFormat.NormalizedShort2:
					return 4;

				case VertexElementFormat.NormalizedShort4:
					return 8;

				case VertexElementFormat.HalfVector2:
					return 4;

				case VertexElementFormat.HalfVector4:
					return 8;
			}
			return 0;
		}

		private int GetVertexStride(VertexElement[] elements)
		{
			int num2 = 0;
			for (int i = 0; i < elements.Length; i++)
			{
				int num3 = elements[i].Offset + GetTypeSize(elements[i].VertexElementFormat);
				if (num2 < num3)
				{
					num2 = num3;
				}
			}
			return num2;
		}

		private void Validate(int vertexStride, VertexElement[] elements)
		{
			if (vertexStride <= 0)
			{
				throw new ArgumentOutOfRangeException("vertexStride");
			}
			if ((vertexStride & 3) != 0)
			{
				throw new ArgumentException(FrameworkResources.VertexElementOffsetNotMultipleFour);
			}
			int[] numArray = new int[vertexStride];
			for (int i = 0; i < vertexStride; i++)
			{
				numArray[i] = -1;
			}
			for (int j = 0; j < elements.Length; j++)
			{
				int offset = elements[j].Offset;
				int typeSize = GetTypeSize(elements[j].VertexElementFormat);
				if ((elements[j].VertexElementUsage < VertexElementUsage.Position) || (elements[j].VertexElementUsage > VertexElementUsage.TessellateFactor))
				{
					throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, FrameworkResources.VertexElementBadUsage, elements[j].VertexElementUsage, string.Empty));
				}
				if ((offset < 0) || ((offset + typeSize) > vertexStride))
				{
					throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, FrameworkResources.VertexElementOutsideStride, elements[j].VertexElementUsage, elements[j].UsageIndex));
				}
				if ((offset & 3) != 0)
				{
					throw new ArgumentException(FrameworkResources.VertexElementOffsetNotMultipleFour);
				}
				for (int k = 0; k < j; k++)
				{
					if ((elements[j].VertexElementUsage == elements[k].VertexElementUsage) && (elements[j].UsageIndex == elements[k].UsageIndex))
					{
						throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, FrameworkResources.DuplicateVertexElement, elements[j].VertexElementUsage, elements[j].UsageIndex));
					}
				}
				for (int m = offset; m < (offset + typeSize); m++)
				{
					if (numArray[m] >= 0)
					{
						throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, FrameworkResources.VertexElementsOverlap, elements[numArray[m]].VertexElementUsage, elements[numArray[m]].UsageIndex, elements[j].VertexElementUsage, elements[j].UsageIndex));
					}
					numArray[m] = j;
				}
			}
		}
		#endregion

		/// <summary>
		/// Gets the vertex shader declaration.
		/// </summary>
		public VertexElement[] GetVertexElements()
		{
			return (VertexElement[])this._elements.Clone();
		}
	}
}
